/*
 * 项目名称:MySpringMVC
 * 类名称:Servlet.java
 * 包名称:com.ly.springMVC
 *
 * 修改履历:
 *      日期                修正者      主要内容
 *      2019-03-22 10:14    apple      初版完成
 *
 * Copyright (c) 2017-2019 咨同科技
 */
package com.ly.springMVC;

import com.alibaba.fastjson.JSON;
import com.ly.springMVC.annotation.*;
import com.ly.springMVC.bean.HandlerModel;
import com.ly.springMVC.until.Play;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 调度Servlet
 *
 * @author apple
 */
public class DispatcherServlet extends HttpServlet {

    /**
     * 存放扫描注解类路径
     */
    private List<String> classNames = new ArrayList();

    /**
     * 实例容器
     */
    private Map<String, Object> instanceMapping = new HashMap<String, Object>();

    /**
     * 映射容器
     */
    private Map<String, HandlerModel> handlerMapping = new HashMap<>();

    @Override
    public void init(ServletConfig servletConfig) throws ServletException {
        String className = this.getClass().getName();
        long startInit = System.currentTimeMillis();
        System.out.println("类名：" + className + "状态：————————初始化开始————————");
        // 扫码包下的所有类
        scanPackage(servletConfig.getInitParameter("scanPackage"));
        // 控制反转，实例化扫描的类
        doInstance();
        // 依赖注入
        doAutoWired();
        // url和类进行映射
        doHandlerMapping();
        System.out.println("类名：" + className + " | 状态： ————————初始化完成———————— | 用时：" + (System.currentTimeMillis() - startInit) + "秒.");
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.setContentType("application/json;charset=utf-8");
        doInvoke(req, resp);

    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        super.doPost(req, resp);
    }


    private void doInvoke(HttpServletRequest request, HttpServletResponse response) {
        String uri = request.getRequestURI();
        try {
            if (uri == null) {
                response.getWriter().write("404 Not Found");
                return;
            }
            HandlerModel handlerModel = handlerMapping.get(uri);
            Method method = handlerModel.getMethod();
            Object invoke = method.invoke(handlerModel.getController(), null);
            if (invoke != null) {
                String msg = JSON.toJSONString(invoke);
                response.getWriter().write(msg);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }

    /**
     * 实现AutoWired功能
     */
    private void doAutoWired() {

        if (instanceMapping.isEmpty()) {
            return;
        }
        // 遍历所有被托管的对象
        for (Map.Entry<String, Object> entry : instanceMapping.entrySet()) {
            Field[] fields = entry.getValue().getClass().getDeclaredFields();
            for (Field field :
                    fields) {
                if (!field.isAnnotationPresent(ZtAutoWired.class)) {
                    continue;
                }
                String beanName;
                ZtAutoWired ztAutoWired = field.getAnnotation(ZtAutoWired.class);
                if ("".equals(ztAutoWired.value())) {
                    beanName = toLowerFirstChar(field.getType().getSimpleName());
                } else {
                    beanName = ztAutoWired.value();
                }
                field.setAccessible(true);
                if (instanceMapping.get(beanName) != null) {
                    try {
                        field.set(entry.getValue(), instanceMapping.get(beanName));
                    } catch (IllegalAccessException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }

    /**
     * 扫描包下所有类
     *
     * @param pkgName
     */
    private void scanPackage(String pkgName) {
        // 获取指定的包的实际路径url，将com.tianyalei.mvc变成目录结构com/tianyalei/mvc
        URL url = getClass().getClassLoader().getResource("/" + pkgName.replaceAll("\\.", "/"));
        // 转化成File对象
        File dir = new File(url.getFile());
        // 递归查到的所有class对象
        for (File file :
                dir.listFiles()) {
            if (file.isDirectory()) {
                scanPackage(pkgName + "." + file.getName());
            } else {
                if (!file.getName().endsWith(".class")) {
                    continue;
                }
                String className = pkgName + "." + file.getName().replace(".class", "");
                try {
                    Class<?> clazz = Class.forName(className);
                    if (clazz.isAnnotationPresent(ZtController.class)
                            || clazz.isAnnotationPresent(ZtRepository.class)
                            || clazz.isAnnotationPresent(ZtService.class)) {

                        classNames.add(className);
                    }
                } catch (ClassNotFoundException e) {
                    e.printStackTrace();
                }
            }

        }
    }

    /**
     * 处理注解是requestMapping的方法或者类
     */
    private void doHandlerMapping() {
        if (instanceMapping.isEmpty()) {
            return;
        }

        // 遍历托管的对象，寻找Controller
        for (Map.Entry<String, Object> entry : instanceMapping.entrySet()) {
            Class<?> clazz = entry.getValue().getClass();
            if (!clazz.isAnnotationPresent(ZtController.class)) {
                continue;
            }
            // 定义url
            String url = "/";
            // 取到RequestMapping的值
            if (clazz.isAnnotationPresent(ZtRequestMapping.class)) {
                ZtRequestMapping ztRequestMapping = clazz.getAnnotation(ZtRequestMapping.class);
                url += ztRequestMapping.value();
            }

            Method[] methods = clazz.getMethods();
            for (Method method :
                    methods) {
                if (!method.isAnnotationPresent(ZtRequestMapping.class)) {
                    continue;
                }
                ZtRequestMapping ztRequestMapping = method.getAnnotation(ZtRequestMapping.class);
                String realUrl = url + "/" + ztRequestMapping.value();
                // 真实RequestMapping
                realUrl = realUrl.replaceAll("/+", "/");

                // 获取参数的注解
                Annotation[][] methodParameterAnnotations = method.getParameterAnnotations();

                HashMap<String, Integer> paramMap = new HashMap<>(16);
                // 获取方法里的所有参数的参数名
                String[] paramNames = Play.getMethodParameterNamesByAsm4(clazz, method);

                // 获取参数的类型，提取Request和response的索引
                Class<?>[] parameterTypes = method.getParameterTypes();
                for (int i = 0; i < methodParameterAnnotations.length; i++) {
                    Annotation[] parameterAnnotation = methodParameterAnnotations[i];
                    if (parameterAnnotation.length == 0) {
                        Class<?> parameterType = parameterTypes[i];
                        if (parameterType == HttpServletRequest.class || parameterType == HttpServletResponse.class) {
                            paramMap.put(parameterType.getName(), i);
                        } else {
                            paramMap.put(paramNames[i], i);
                        }
                        continue;
                    }

                    for (Annotation annotation : parameterAnnotation) {
                        if (annotation.annotationType() == ZtRequestParam.class) {
                            ZtRequestParam ztRequestParam = (ZtRequestParam) annotation;
                            String paramValue = ztRequestParam.value();
                            if (!"".equals(paramValue)) {
                                paramMap.put(paramValue, i);
                            }
                        }
                    }
                }
                HandlerModel handlerModel = new HandlerModel(method, entry.getValue(), paramMap);
                handlerMapping.put(realUrl, handlerModel);
            }
        }
    }

    /**
     * 实例化扫描的类
     */
    private void doInstance() {
        if (classNames.size() == 0) {
            return;
        }
        for (String className : classNames) {
            try {
                Class<?> clazz = Class.forName(className);
                if (clazz.isAnnotationPresent(ZtController.class)) {
                    // 实例化一个对象，对象是类名的首字母小写
                    instanceMapping.put(toLowerFirstChar(clazz.getSimpleName()), clazz.newInstance());
                } else if (clazz.isAnnotationPresent(ZtService.class) || clazz.isAnnotationPresent(ZtRepository.class)) {
                    String serviceName;
                    ZtService ztService = clazz.getAnnotation(ZtService.class);
                    ZtRepository ztRepository = clazz.getAnnotation(ZtRepository.class);
                    if (ztService != null) {
                        serviceName = ztService.value();
                    } else {
                        serviceName = ztRepository.value();
                    }
                    // 如果有值，就以该值为Key
                    if (!"".equals(serviceName.trim())) {
                        instanceMapping.put(serviceName, clazz.newInstance());
                    } else {
                        // 获取它的接口
                        Class<?>[] interfaces = clazz.getInterfaces();
                        // 简化处理，默认认为他只有实现一个接口asm.jar
                        for (Class c :
                                interfaces) {
                            instanceMapping.put(toLowerFirstChar(c.getSimpleName()), clazz.newInstance());
                            break;
                        }

                    }
                }
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InstantiationException e) {
                e.printStackTrace();
            }

        }
    }

    /**
     * 转换首字母小写
     *
     * @param s
     * @return
     */
    private String toLowerFirstChar(String s) {
        if (Character.isLowerCase(s.charAt(0))) {
            return s;
        } else {
            return (new StringBuilder()).append(Character.toLowerCase(s.charAt(0))).append(s.substring(1)).toString();
        }
    }

}